package com.remoting.core.http.httpclient;

import com.remoting.api.RemoteSetting;
import com.remoting.api.http.HttpHandler;
import com.remoting.api.registry.bean.HeaderInfo;
import com.remoting.api.request.support.GetRequest;
import com.remoting.api.request.support.PostRequest;
import com.remoting.core.exception.RemoteException;
import com.remoting.core.support.RemoteContentType;
import com.remoting.tools.FastJsonUtil;
import com.remoting.tools.XMLUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.springframework.util.StreamUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * HTT请求处理器
 * <p>
 * 说明:
 * </p>
 * <li>基于HttpClient</li>
 * @author DuanYong
 * @since 2017年3月3日上午10:17:31
 */
public class HttpClientHandler implements HttpHandler {
	protected static final Log logger = LogFactory.getLog(HttpClientHandler.class);
	private static final String UTF8 = "UTF-8";
	/**远程配置*/
	private RemoteSetting remoteSetting;
	/**HttpClient*/
	public static CloseableHttpClient httpClient = null;
	
    /**
     * POST请求
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @param request 请求参数
     * @return 响应数据
     * @since 2018年2月2日上午9:06:02
     */
	public String post(PostRequest request) {
		return doPost(request.getAddress(),request.getServiceImportInfo().getHeaderInfo(), request.getParams());
	}
	/**
	 * get请求
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param reques 请求参数
	 * @return 响应数据
	 * @since 2018年2月2日上午9:06:21
	 */
   	public String get(GetRequest reques) {
		return doGet(reques.getAddress(),reques.getServiceImportInfo().getHeaderInfo());
	}

	/**
	 * 执行post请求
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param url
	 * @param headerInfo
	 * @param data
	 * @return
	 * @since 2018年2月2日上午9:05:17
	 */
	private  String doPost(String url, HeaderInfo headerInfo, Map<String,Object> data) {
		HttpPost post = new HttpPost(url);
		CloseableHttpResponse httpResponse = null;
		String jsonStr = "";
		Long startTime = System.currentTimeMillis();
		try {
			jsonStr = FastJsonUtil.toJSONString(data);
		}catch(Exception e){
			e.printStackTrace();
			throw new RemoteException("请求参数转JSON失败!", e);
		}
		try {
			for(Entry<String,String> entry : headerInfo.getHeaderMap().entrySet()){
				post.setHeader(entry.getKey(), entry.getValue());
			}
			try {
				HttpEntity entity = getEntityByContentType(headerInfo, data);
				post.setEntity(entity);
				httpResponse = httpClient.execute(post);
			} catch (Exception e) {
				logger.error("发送远程服务调用请求时发生异常,请求URL:"+url+",POST参数:"+jsonStr+",信息："+e);
				e.printStackTrace();
				throw new RemoteException("发送远程服务调用请求时发生异常", e);
			}
			if(logger.isErrorEnabled()){
				logger.error(url+",(参数:"+jsonStr+"),请求完成,耗时->"+(System.currentTimeMillis() - startTime)/1000.0+"秒");
			}
			//读取响应结果
			try {
				return getResponse(httpResponse);
			} catch (Exception e) {
				throw new RemoteException("交易处理发生一些异常，无法获取响应结果!", e);
			}
		} finally {
			try {
				if (null != httpResponse) {
					httpResponse.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
	}
	/**
	 * 根据类型获取相应HttpEntity
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param headerInfo
	 * @param data
	 * @return
	 * @throws UnsupportedEncodingException
	 * @since 2018年7月23日下午2:24:01
	 */
	private HttpEntity getEntityByContentType(HeaderInfo headerInfo, Map<String,Object> data) throws UnsupportedEncodingException {
		HttpEntity entity = null;
		if(RemoteContentType.FORM.getValue().equalsIgnoreCase(headerInfo.getHeaderMap().get(HTTP.CONTENT_TYPE))){
			entity = getUrlEncodedFormEntity(headerInfo,data);
		}else if(RemoteContentType.FORM_DATA.getValue().equalsIgnoreCase(headerInfo.getHeaderMap().get(HTTP.CONTENT_TYPE))){
			entity = getMultipartEntity(headerInfo,data);
		}else if(RemoteContentType.TEXT_XML.getValue().equalsIgnoreCase(headerInfo.getHeaderMap().get(HTTP.CONTENT_TYPE))){
			entity = getXmlEntity(headerInfo,data);
		}else{
			entity = getStringEntity(headerInfo,data);
		}
		return entity;
	}
	/**
	 * 获取jsonHttpEntity
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param headerInfo
	 * @param data
	 * @return
	 * @since 2018年7月23日上午10:56:36
	 */
	private HttpEntity getStringEntity(HeaderInfo headerInfo, Map<String,Object> data) {
		String jsonStr = "";
		try {
			jsonStr  = FastJsonUtil.toJSONString(data);
		}catch(Exception e){
			e.printStackTrace();
			throw new RemoteException("请求参数转JSON失败!", e);
		}
		return new StringEntity(jsonStr, headerInfo.getCharset());
	}
	/**
	 * 获取XML HttpEntity
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param headerInfo
	 * @param data
	 * @return
	 * @throws UnsupportedEncodingException
	 * @since 2018年7月23日上午10:57:07
	 */
	private HttpEntity getXmlEntity(HeaderInfo headerInfo, Map<String,Object> data) throws UnsupportedEncodingException {
		List<BasicNameValuePair> parameters = new ArrayList<>();
		parameters.add(new BasicNameValuePair("xml", XMLUtil.map2Xml(data)));
		return new UrlEncodedFormEntity(parameters,headerInfo.getCharset());
	}
	/**
	 * 获取 MultipartEntity
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param headerInfo
	 * @param data
	 * @return
	 * @since 2018年7月23日上午10:57:22
	 */
	private HttpEntity getMultipartEntity(HeaderInfo headerInfo, Map<String,Object> data) {
		MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.setCharset(java.nio.charset.Charset.forName(headerInfo.getCharset()));
        builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
        //解决中文乱码
        ContentType contentType = ContentType.create(ContentType.TEXT_PLAIN.toString(), headerInfo.getCharset());
        for (Entry<String, Object> entry : data.entrySet()) {
            if(entry.getValue() == null){
            	continue;
            }
            // 类似浏览器表单提交，对应input的name和value
            builder.addTextBody(entry.getKey(), entry.getValue().toString(), contentType);
        }
        return builder.build();
	}
    /**
     * 获取 UrlEncodedFormEntity
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @param headerInfo
     * @param data
     * @return
     * @throws UnsupportedEncodingException
     * @since 2018年7月23日上午10:57:40
     */
	private HttpEntity getUrlEncodedFormEntity(HeaderInfo headerInfo, Map<String, Object> data) throws UnsupportedEncodingException {
		// 用于存放表单数据
		List<BasicNameValuePair> pairs = new ArrayList<>(10);
		// 遍历map 将其中的数据转化为表单数据
		for (Entry<String, Object> entry : data.entrySet()) {
			if(entry.getValue() == null){
            	continue;
            }
			pairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
		}
		// 对表单数据进行url编码
		return new UrlEncodedFormEntity(pairs);
	}
	/**
	 * 执行get请求
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param url 请求地址
	 * @param headerInfo 头信息
	 * @return 响应数据
	 * @since 2018年2月2日上午9:07:24
	 */
	private String doGet(String url,HeaderInfo headerInfo) {
		HttpGet get = new HttpGet(url);
		CloseableHttpResponse httpResponse = null;
		Long startTime = System.currentTimeMillis();
		try {
			try {
				for(Entry<String,String> entry : headerInfo.getHeaderMap().entrySet()){
					get.setHeader(entry.getKey(), entry.getValue());
				}
				httpResponse = httpClient.execute(get);
			} catch (Exception e) {
				logger.error("发送远程服务调用请求时发生异常,请求URL:"+url+",信息："+e);
				e.printStackTrace();
				throw new RemoteException("发送远程服务调用请求时发生异常", e);
			}
			if(logger.isErrorEnabled()){
				logger.error(url+",请求完成,耗时->"+(System.currentTimeMillis() - startTime)/1000.0+"秒");
			}
			/**
			 * 读取响应结果
			 */
			try {
				return getResponse(httpResponse);
			} catch (Exception e) {
				throw new RemoteException("交易处理发生有一些异常，无法获取响应结果!", e);
			}
		} finally {
			try {
				if (null != httpResponse) {
					httpResponse.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
    /**
     * 获取HttpClient
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @return
     * @since 2017年6月21日上午9:43:20
     */
	private CloseableHttpClient getHttpClient() {
		CloseableHttpClient client;
		// 设置协议http和https对应的处理socket链接工厂的对象  
	    Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
	          .register("http", PlainConnectionSocketFactory.INSTANCE)
	          .register("https", getSSLConnectionSocketFactory())  
	          .build();  
		// 初始化线程池
		PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
	    pccm.setMaxTotal(remoteSetting.getMaxTotal()); 
	 // 单路由最大并发数
	    pccm.setDefaultMaxPerRoute(remoteSetting.getDefaultMaxPerRoute()); 
	    //配置请求的超时设置
	    RequestConfig requestConfig = RequestConfig.custom().setExpectContinueEnabled(true)
				.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
				.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
				.setSocketTimeout(remoteSetting.getSocketTimeout())
				.setConnectTimeout(remoteSetting.getConnectionTimeout())
				.setConnectionRequestTimeout(remoteSetting.getConnectionRequestTimeout())
				.build();
	    //构建
		client = HttpClients.custom()
				.setConnectionManager(pccm)
				.setDefaultRequestConfig(requestConfig)
				.setRetryHandler(new DefaultHttpRequestRetryHandler())
				.build();// 默认失败后重发3次
		return client;
	}
	
	/**
	 * 获取SSLConnectionSocketFactory
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @return
	 * @since 2017年8月31日上午11:40:30
	 */
	private SSLConnectionSocketFactory getSSLConnectionSocketFactory(){
		try {
			return new SSLConnectionSocketFactory(createIgnoreVerifySSL());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	 /**
     * 绕过验证 
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @return
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     * @since 2017年8月31日下午3:40:59
     */
	private static SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
		SSLContext sc = SSLContext.getInstance("SSLv3");
		// 实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法
		X509TrustManager trustManager = new X509TrustManager() {
			@Override
			public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
					String paramString) throws CertificateException {
			}

			@Override
			public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
					String paramString) throws CertificateException {
			}

			@Override
			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return null;
			}
		};

		sc.init(null, new TrustManager[] { trustManager }, null);
		return sc;
	}
    /**
     * 获取响应结果
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @param httpResponse
     * @return
     * @throws IOException
     * @throws UnsupportedEncodingException
     * @since 2017年6月21日上午10:26:11
     */
	private String getResponse(HttpResponse httpResponse) throws IOException, UnsupportedEncodingException {
		HttpEntity entity = httpResponse.getEntity();
		InputStream is = entity.getContent();
		
		ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
		StreamUtils.copy(is, out);
		String responseCharset = ((entity.getContentEncoding() == null
				|| entity.getContentEncoding().getValue() == null) ? UTF8
						: entity.getContentEncoding().getValue());
		String result = new String(out.toByteArray(), responseCharset);
		if(logger.isInfoEnabled()){
			logger.info("响应结果:"+result);
		}
		return result;
	}
	@Override
	public HttpHandler init(RemoteSetting remoteSetting) {
		this.remoteSetting = remoteSetting;
		httpClient = getHttpClient();
		return this;
	}
}
